
module DrunkenMaster
  
  # Widget represent a visual interactive unit.
  # It decorates Element.
  class Widget < Element
   
    # The main action that is going to be called as a default one.
    def index
    end
    
    # Every handler can define _be_plugged_ to do his own pluging.
    # example: router plugs file handler, then plugs itself.
    # 
    #  def self.be_plugged router
    #    router.plug public_files_handler,  '/'
    #    router.plug self,                  '/'
    #  end
    #
    def self.be_plugged router
      router.plug public_files_handler,  '/'
      router.plug self,                  '/'
    end    

    # Event from router. Here we plug the special WidgetFileHandler
    # under our own path where we were plugged.
    def self.on_plug router, path
      router.plug WidgetFileHandler.new(self), '/widget_file'
      router.plug WidgetCallbackHandler.new(self), '/callback'
      self.plugged_at = path
    end    
    
    # Initializes FileHandler for widget's publc folder.
    def self.public_files_handler
      DrunkenMaster::FileHandler.new(
        Loader::Load.dir_of(self, Magic.module_of(self))+'/public')
    end
    
    def initialize(*args)      
      if args.length > 2
        raise "Too many arguments #{args.inspect}."
      end
      attrs = args.pop || {}
      tag_name = args.pop || 'div'
      super(tag_name, attrs)
      add_class(widget_classes)
    end

    def widget_classes
      class_names = self.class.ancestors[0, self.class.ancestors.index(Widget)]
      class_names.collect { |c| c.to_s.split('::').last }
    end

    ## control
    
    # Might put some cache in here!
    def self.execute(*args)
      self.new.execute(*args)
    end

    
    # Execute is called on every handler to set response. 
    # In Widget's case it means to build the page out of other composits,
    # propagate the url tail and call opropriate actions.
    def execute(context)
      self.context = context
      propagate_url #context.tail
      if @__response.nil?
        context.page.body.innerHTML = self.render
        context.response.body = context.page.render.join()
      else
        if @__response.is_a? String
          context.response.body = @__response
        else
          context.response = @__response
        end        
      end
      context.response
    end
    
    # Renders the widgen into an array of strings.
    def render
      #clean_state_children = self.children.clone
      #warn "No action called on #{self.class}!" unless @__called_action
      super().tap {
        self.id = nil
      }
    end


    # Eery widget is asked to identify a portion of url.
    # By default, this means that if its not a View, it will
    # identify the action of the widget. (index by default)
    def handle_url chunks
      action = chunks[0] || :index
      eaten = 1
      # forward compatability with ruby 1.9 
      if public_methods.include?(action.to_sym) ||
          public_methods.include?(action.to_s)
        args = []
        if method(action.to_s).arity == 1
          args = [chunks[1] || nil]
          eaten += 1
        end
        call_action(action.to_sym, *args)
        eaten
      else
        #raise UnknownActionException.new(action, self)
        call_action(:index)
        0
      end
    end
    
    class UnknownActionException < Exception
      def initialize action, widget
        super "No action named '#{action}' in #{widget.class.to_s}."
      end
    end

    def call_action name, *args
      build
      @__called_action = name
      self.send(name, *args)
    end

    # Makes a url leading to _last_node_.
    # The url that led to this state will be used again 
    # with the new _last_node_ poriton.
    def url last_node
      expect_context
      node = self.parent
      url = []
      while node != nil
        unless node == self.parent
          url.push(*node.recognized_chunks)
        end
        node = node.parent
      end
      File.join(plugged_at, url.compact, last_node.to_s)
    end

    def link name, url, attrs = {}
      e :a, {
        :innerHTML => name,
        :href => url(url)
      }.merge(attrs)
    end
    
    # Makes special url for a file thats under widget's
    # own public directory.
    def file_url filename
      public_file_url(filename) || widget_file_url(filename)
    end
    
    # Returns url for file under widgets own directory.
    def widget_file_url filename
      dir = Loader::Load.dir_of(self.class)
      file = dir + '/public/' + filename
      if File.exists?(file)
        File.join(plugged_at, 'widget_file', camelized_class, filename)
      else
        # throw exception if there is no such file.
        # Its better not to wait for someone actually
        # clicking that link or using the file.
        raise "Couldn't find file #{file}."
      end      
    end
    
    # Returns url for file under /public directory of the _root_widget_.
    def public_file_url filename
      file = Loader::Load.dir_of(root_widget.class) + '/public/' + filename
      if File.exists?(file)
        return File.join(plugged_at, filename)
      end
      false
    end
    
    # Makes a direct url to widget's action.
    def callback action
      '/'+File.join('callback', camelized_whole_class, action.to_s)
    end

    def redirect url
      context.response.code = 303
      context.response.headers["Location"] = url
      respond context.response
    end

    # Sets the response. 
    # When the response is set,
    # no further actions are be executed
    # and response is returned.
    def respond response      
      @__response = response
    end

    
    ## state related

    # Creates state variables with their default values and\
    # persists them in session.
    # 
    #   class Ball < Widget
    #     state_defined_by :color => 'blue', :bouncy => true
    #   end
    def self.state_defined_by map
      raise 'Give me hash.' unless map.is_a?(Hash)
      @@_state_defined_by ||= {}
      @@_state_defined_by[self.to_s] = map.keys
      map.each_pair do |state, default_value|
        class_eval "def #{state}= val
          session[:#{state}] = val
        end"
        #p s
        class_eval "def #{state}
          session[:#{state}] ||= #{default_value.inspect}
        end"
      end
    end
    
    # Returns variable names which define the state of the object.
    def state_defined_by
      @@_state_defined_by[self.class.to_s]
    end

    # Returns session data for this widget.
    # Can be though of as class variables.
    def session
      expect_context
      context.rack_env['rack.session'][self.class.to_s] ||= {}
    end
    
    # Returns state of this object (currently the session)
    def state
      session
    end
    
    
  private  
  
    # Returns camelized name of class.
    # Demo::CodeWidget -> code_widget
    def camelized_class
      Util::CamelCase.from(self.class.to_s.split('::').last)
    end

    def camelized_whole_class
      Util::CamelCase.from(self.class.to_s.split('::').join('-'))
    end
    
    ## url related
    
    def plugged_at      
      @@__plugged_at ||= {}
      fixme unless @@__plugged_at[root_widget.class]
      @@__plugged_at[root_widget.class] || ""
    end
    
    def self.plugged_at= path
      slot = self.is_a?(Class) ? self : self.class
      @@__plugged_at ||= {}
      @@__plugged_at[slot] = path
    end
    
    # Returns parent widget that is root to this composite tree. 
    # (The widget is plugged into the router.)
    def root_widget
      return @__root_widget if @__root_widget
      widget = self
      while widget.parent != nil && widget.parent.tag_name != 'body'
        widget = widget.parent
      end
      @__root_widget = widget
    end
    
    
  end
end